#include "httpresponse.h"

using namespace std;
//文件类型与MIME类型的映射关系
const unordered_map<string, string> HttpResponse::SUFFIX_TYPE = {
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css "},
    { ".js",    "text/javascript "},
};
//HTTP状态码与状态描述的映射关系
const unordered_map<int, string> HttpResponse::CODE_STATUS = {
    { 200, "OK" },
    { 400, "Bad Request" },
    { 403, "Forbidden" },
    { 404, "Not Found" },
};
//HTTP状态码与错误页面路径的映射关系
const unordered_map<int, string> HttpResponse::CODE_PATH = {
    { 400, "/400.html" },
    { 403, "/403.html" },
    { 404, "/404.html" },
};
//构造函数，用来初始化HttpResponse对象
HttpResponse::HttpResponse()
{
    //将状态码设置为-1，代表初始状态
    code_ = -1;
    //将路径和资源目录路径设置为空字符串
    path_ = srcDir_ = "";
    //将是否保持长连接设置为false
    isKeepAlive_ = false;
    //将内存映射的文件指针设置为空
    mmFile_ = nullptr;
    //将文件状态结构体清零
    mmFileStat_ = {0};
}
//析构函数，用来释放HttpResponse对象占用的资源
HttpResponse::~HttpResponse()
{
    //调用UnmapFile方法解除文件的内存映射
    UnmapFile();
}
//初始化HttpResponse对象，srcDir是资源目录路径，path是请求的文件路径，isKeepAlive是是否保持长连接，code是HTTP的状态码
void HttpResponse::Init(const std::string& srcDir,std::string& path,bool isKeepAlive,int code)
{
    //断言判断资源目录路径是否为空
    assert(srcDir != "");
    //如果内存映射的文件指针不为空，先解除内存映射
    if(mmFile_) {UnmapFile();}
    //给状态码赋值
    code_ = code;
    //给是否保持长连接赋值
    isKeepAlive_ = isKeepAlive;
    //给请求的文件路径赋值
    path_ = path;
    //给资源目录赋值
    srcDir_ = srcDir;
    //给内存映射的文件指针赋值为空
    mmFile_ = nullptr;
    //将文件状态结构体清零
    mmFileStat_ = {0};
}

//构建完整的HTTP响应并写入到输出缓冲区
//buff是输出缓冲区
//依次调用私有方法添加状态行、头部和内容
void HttpResponse::MakeResponse(Buffer& buff)
{
    //如果找不到文件，或者是目录，设置状态码为404
    if(stat((srcDir_ + path_).data(),&mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode))
    {
        //将状态码设置为404
        code_ = 404;
    }
    //如果文件没有读取权限，设置状态码为403
    else if(!(mmFileStat_.st_mode & S_IROTH))
    {
        //将状态码设置为403
        code_ = 403;
    }
    //如果状态码为-1，设置为200
    else if(code_ == -1)
    {
        //将状态码设置为200
        code_ = 200;
    }
    //生成错误页面的HTML内容
    ErrorHtml_();
    //添加状态行
    AddStateLine_(buff);
    //添加头部
    AddHeader_(buff);
    //添加内容
    AddContent_(buff);
}
//获取内存映射的文件指针，用于直接访问文件内容
char* HttpResponse::File()
{
    //返回内存映射的文件指针
    return mmFile_;
}
//返回内存映射的文件长度
size_t HttpResponse::FileLen() const 
{
    //返回内存映射的文件长度
    return mmFileStat_.st_size;
}
//生成错误页面的HTML内容
void HttpResponse::ErrorHtml_()
{
    //如果状态码在错误页面路径映射中
    if(CODE_PATH.count(code_) == 1)
    {
        //将路径设置为CODE_PATH中，根据状态码，对应的路径
        path_ = CODE_PATH.find(code_)->second;
        //获取页面的文件状态
        stat((srcDir_+path_).data(),&mmFileStat_);
    }
}
//添加状态行
void HttpResponse::AddStateLine_(Buffer& buff)
{
    //创建一个字符串，用来存储状态描述
    string status;
    //如果状态码在状态描述映射中
    if(CODE_STATUS.count(code_) == 1)
    {
        //将状态描述设置为CODE_STATUS中，根据状态码，对应的状态描述
        status = CODE_STATUS.find(code_)->second;
    }
    //如果状态码不在状态描述映射中
    else
    {
        //将状态码设置为400
        code_ = 400;
        //将状态描述设置为400，对应的状态描述
        status = CODE_STATUS.find(400)->second;
    }
    //将状态行添加到缓冲区
    buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n");
}
//添加头部
void HttpResponse::AddHeader_(Buffer& buff)
{
    //添加连接头部
    buff.Append("Connection: ");
    //如果是保持长连接
    if(isKeepAlive_)
    {
        //添加保持长连接头部
        buff.Append("keep-alive\r\n");
        //添加保持长连接超时时间头部
        buff.Append("keep-alive: max=6, timeout=120\r\n");
    }
    //如果不保持长连接
    else     
    {
        //添加关闭连接头部
        buff.Append("close\r\n");
    }
    //添加内容类型头部
    buff.Append("Content-type: " + GetFileType_() + "\r\n");
}
//添加内容
void HttpResponse::AddContent_(Buffer& buff)
{
    //打开目标文件，并将文件描述符赋值给srcFd
    int srcFd = open((srcDir_ + path_).data(),O_RDONLY);
    //如果打开文件失败
    if(srcFd<0)
    {
        //添加错误内容
        ErrorContent(buff,"File NotFound!");
        //返回
        return;
    }
    //如果打开文件成功，输出日志信息
    LOG_DEBUG("file path:%s",(srcDir_ + path_).data());
    //将文件映射到内存中
    void* mmRet = mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0);
    //如果文件映射失败
    if(mmRet == MAP_FAILED)
    {
        //添加错误内容
        ErrorContent(buff,"File NotFound!");
        //返回
        return;
    }
    //将文件映射指针赋值给mmFile_
    mmFile_ = static_cast<char*>(mmRet);
    //关闭文件描述符
    close(srcFd);
    //添加内容长度头部
    buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n");
}
//解除文件的内存映射，释放资源
void HttpResponse::UnmapFile()
{
    //确保内存映射的文件指针不为空且文件长度有效
    if(mmFile_ != nullptr && mmFileStat_.st_size > 0)
    {
        //解除内存映射
        munmap(mmFile_, mmFileStat_.st_size);
        //将内存映射指针设置为空
        mmFile_ = nullptr;
        //重置文件状态结构体
        mmFileStat_ = {0};
    }
}
//获取文件的类型，根据文件后缀名判断
string HttpResponse::GetFileType_()
{
    //查找路径中最后一个点的位置
    string::size_type idx = path_.find_last_of('.');
    //如果没有找到点，返回默认类型
    if(idx == string::npos)
    {
        //如果没有找到点，返回默认类型
        return "text/plain";
    }
    //如果找到点，获取后缀名
    string suffix = path_.substr(idx);
    //如果后缀名在后缀类型映射中
    if(SUFFIX_TYPE.count(suffix) == 1)
    {
        //如果后缀名在后缀类型映射中，返回对应的类型
        return SUFFIX_TYPE.find(suffix)->second;
    }
    //如果不在后缀类型映射中，返回默认类型
    return "text/plain";
}
//添加错误内容
void HttpResponse::ErrorContent(Buffer& buff, string message) 
{
    string body;
    string status;
    body += "<html><title>Error</title>";
    body += "<body bgcolor=\"ffffff\">";
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;
    } else {
        status = "Bad Request";
    }
    body += to_string(code_) + " : " + status  + "\n";
    body += "<p>" + message + "</p>";
    body += "<hr><em>TinyWebServer</em></body></html>";

    buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n");
    buff.Append(body);
}